/**
 * @file    app_user.c
 * @author  Azolla (1228449928@qq.com)
 * @brief
 * @version 0.1
 * @date    2022-09-19
 * 
 * @copyright Copyright (c) 2022
 * */
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "mbedtls/base64.h"

#include "ble_gatts.h"
#include "ble_gattc.h"

#include "wifi_mesh.h"
#include "wifi_sock.h"
#include "wifi_sntp.h"
#include "wifi_user.h"
#include "wifi_mupgrade.h"
#include "wifi_mqtt.h"
 
#include "app_user.h"
 
#define CONFIG_LVGL_ENABLE          1     // 1: 使用LVGL
 
#if !CONFIG_LVGL_ENABLE 
#include "app_uart.h"
#include "app_voice.h"
#include "app_rgb.h"
#else 
#include "app_lvgl.h"
#include "audio_board_init.h"
#endif

#include "app_scene.h"

#define TAG   "app_user"


#define APP_USER_DEBUG_ENABLE       1
 
//==============================================================================================================
//==============================================================================================================
static nvs_wifi_t nvs_wifi;
#if APP_CONFIG_MQTT_ENABLE
static nvs_mqtt_t nvs_mqtt;
#endif   

static self_info_t self;

static struct tm *sntp;

static uint8_t audio_voice = 0;   // 音频
static bool tumble_status = 0;  // 1: 有人跌倒
static uint8_t tumble_time = 0;
 
static bool sys_config_network = 0; // 0：未配网;  1: 配网成功;
static bool sys_reset_enable = 0;    // 1: 软件复位
 
#if !CONFIG_LVGL_ENABLE 
static hsv_t hsv = { 0 };
#endif

#define SCENE_LOCAL_ENABLE  0   // 1：支持本地离线情景？
 
#if SCENE_LOCAL_ENABLE
static scene_trigger_t trigger;      // 情景触发条件

typedef struct {
    char name[SCENE_NAME_LEN];     // 情景名
    char action[6];   // 情景当前执行的动作
    bool start;       // 1: 开始执行情景
    uint16_t dtime;   // 持续时间/s
    uint16_t time;    // 时间计数/s
    uint16_t interval;// 时间间隔/S，interval = dtime / (set_mvol - mvol)
    uint8_t mvol;     // 当前音量大小
    uint8_t set_mvol; // 设置的音量大小
} scene_action_t;
static scene_action_t s_action = { 0 };  // 情景执行动作；同一时刻只支持单情景执行
#else 
typedef struct {
    uint16_t time;    // 时间计数/s
    uint16_t dtime;   // 持续时间/s
    uint16_t interval;// 时间间隔/S，interval = dtime / (set_mvol - mvol)
    uint8_t  cvol;    // 当前音量大小
    uint8_t  svol;    // 设置的音量大小
    uint8_t  play;    // 播放第几首音乐
} music_mode_t;
static music_mode_t music_mode = { 0 };  // 音乐模式 
#endif
 

static char mid_value[14];      // 消息ID字符串值；10位时间戳 + 3位编码
static char mid_ota_value[14];  // OTA时的消息ID值
 
//==============================================================================================================
//==============================================================================================================
static char *motor_handler(app_parse_data_t item); 
static char *clear_handler(app_parse_data_t item);
#if SCENE_LOCAL_ENABLE
static char *srun_handler(app_parse_data_t item);
#endif
//==============================================================================================================
//==============================================================================================================
static const uint8_t USER_BLE_EVENT   = BIT0;
static const uint8_t USER_MESH_EVENT  = BIT1;
#if SCENE_LOCAL_ENABLE
static const uint8_t USER_SCENE_EVENT = BIT2;
#endif
static EventGroupHandle_t xEvent = NULL;   // BLE/MESH设备响应事件组！

SemaphoreHandle_t xSemap = NULL; 

// 等待BLE/MESH设备消息响应事件组 
bool user_event_wait(uint8_t event, uint32_t wait_time)
{
    if (xEvent == NULL) return false;
    EventBits_t uxBits = xEventGroupWaitBits(xEvent, event, pdTRUE, pdFALSE, wait_time);
    if (uxBits & event) {
        return true;
    }
    return false;
}
#if !CONFIG_LVGL_ENABLE
#define KEY_RESET_FACTORY   0       // IO0
#define KEY_CLEAR_NETWORK   21      // IO21
static const uint8_t key_table[2] = {KEY_RESET_FACTORY,  KEY_CLEAR_NETWORK};
static uint8_t key_scan_task(void)
{
    for (uint8_t i = 0; i < 2; i++) {
        if (gpio_get_level(key_table[i]) == 0) {
            return key_table[i];
        }
    }
    return 0xFF;
}
#endif

#if !APP_CONFIG_MQTT_ENABLE
// UDP定时广播
static bool upd_fixed_time_adv(uint8_t adv_time)
{
    static uint8_t adv_timecnt = 0;
    if (++adv_timecnt < adv_time) {  // 定时广播/100ms
        return 0;
    } adv_timecnt = 0;

    if (wifi_connect_status(0) == 0) return 0;  // 掉线了
    if (tcp_client_connect_status() == true) return 0;  // TCP连接上了
 
    char adv_data[48];
    uint8_t adv_len = sprintf(adv_data, "{\"%s\":\""ADDRSTR"\"}", self.dev_name, ADDR2STR(self.addr));

    udp_client_write(adv_data, adv_len);
 
    return 1;
}
#endif

void esp_free_heap_print(void)
{
    #if 1  // debug
    ESP_LOGI(TAG, "Free heap, current: %d, minimum: %d", 
                esp_get_free_heap_size(), 
                esp_get_minimum_free_heap_size());  // 打印内存
    ESP_LOGI(TAG, "MALLOC_CAP_SPIRAM = %d, MALLOC_CAP_INTERNAL = %d\n\n", 
                heap_caps_get_free_size(MALLOC_CAP_SPIRAM), 
                heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
    #endif
}
#if SCENE_LOCAL_ENABLE
// 更新情景新动作
bool scene_update_action(const char *name, const char *action)
{
    if (s_action.dtime > 0 && strcmp(action, SCENE_ACTION_START) == 0) return false;
    user_event_wait(USER_SCENE_EVENT, portMAX_DELAY);  // 等待情景执行完成！
    strcpy(s_action.name, name);
    strcpy(s_action.action, action);
    s_action.name[ strlen(name) ] = '\0';
    s_action.action[ strlen(action) ] = '\0';
    s_action.start = true;  // 开始执行
    return true;
}
 
// 情景处理任务
void scene_handle_task(void)    
{
    /* 如果情景有更新，需要重新读条件触发条件！*/ 
    if ( scene_update_event() == true ) {  // 有更新
        scene_read_tigger(&trigger);  // 重新读取触发条件
        xEventGroupSetBits(xEvent, USER_SCENE_EVENT);  // 执行情景模式完成
    } 

    /* 情景之定时器触发 */
    static uint16_t time1s_cnt = 0;
    if (++time1s_cnt >= 10) {  // 1s/单位100ms
        time1s_cnt = 0;
        sntp = sntp_get_time();  // 读取时间
        #if CONFIG_LVGL_ENABLE
        lv_time_handle(sntp);  // LV时间显示
        #endif
        if (sntp->tm_year < 2023) return;   // 时间没校准
         
        /* 情景持续时间结束 */
        if (s_action.dtime > 0) {
            if (--s_action.dtime == 0) {  // 等待情景持续时间结束
                scene_update_action(s_action.name, SCENE_ACTION_STOP);
            } else {  // 持续时间中
                if (++s_action.time >= s_action.interval) {
                    s_action.time = 0;
                    // s_action.mvol = audio_music_get_volume();
                    if (s_action.mvol < s_action.set_mvol) {
                        s_action.mvol++;
                    } else if (s_action.mvol > s_action.set_mvol) {
                        s_action.mvol--;
                    }
                    if (s_action.mvol != s_action.set_mvol) {
                        app_music_play_vol(s_action.mvol); 
                    }
                }
            }
        }
    }

#if 1
    if (s_action.start == true) {  // 触发情景动作
        s_action.start = false;
        xEventGroupClearBits(xEvent, USER_SCENE_EVENT);  // 当前正在执行情景模式
        vTaskDelay(200);  // 稍微延时一下再执行
        int ret = app_scene_action(s_action.name, s_action.action); // 执行情景动作
        if (ret >= 0) {  // ok
            if (strcmp(s_action.action, SCENE_ACTION_STOP) == 0) {  // 是暂停情景
                s_action.dtime = 0;
                app_lv_scene_close();  // LVGL提示情景关闭
            } else {  // SCENE_ACTION_START
                for (uint8_t i = 0; i < trigger.total; i++) {
                    if (trigger.event[i].dtime && s_action.set_mvol != s_action.mvol &&  // Integer Divide By Zero
                        strcmp(s_action.name, trigger.event[i].name) == 0) {  // 查找该情景名下是否有持续时间！
                        s_action.dtime = trigger.event[i].dtime * 60;  // 持续时间/s
                        s_action.interval = (s_action.dtime * 9 / 10) / abs(s_action.set_mvol - s_action.mvol) + 1; // 提前90%的持续时间！
                        s_action.time = 0;
                        #ifdef APP_USER_DEBUG_ENABLE 
                        ESP_LOGI(TAG, "s_action.dtime = %ds", s_action.dtime);
                        ESP_LOGI(TAG, "s_action.interval = %ds", s_action.interval);
                        #endif
                        app_lv_scene_create(s_action.name);  // LVGL提示情景正在执行中
                        break;
                    }
                }
            }
        }
        xEventGroupSetBits(xEvent, USER_SCENE_EVENT);  // 执行情景模式完成
    }
#endif
} 
#else // 在线情景
// 情景处理任务
void scene_handle_task(void)    
{
    /* 情景之定时器触发 */
    static uint16_t time1s_cnt = 0;
    if (++time1s_cnt >= 10) {  // 1s/单位100ms
        time1s_cnt = 0;
        sntp = sntp_get_time();  // 读取时间
        #if CONFIG_LVGL_ENABLE
        lv_time_handle(sntp);  // LV时间显示
        #endif
        // if (sntp->tm_year < 2023) return;   // 时间没校准

        /* 音乐情景模式处理 */
        if (music_mode.dtime > 0) {
            if (--music_mode.dtime == 0) {  // 等待持续时间结束
                // app_music_play_ctrl(false, AUDIO_CTRL_PAUSED);  // 暂停播放
            } else if (++music_mode.time >= music_mode.interval) {// 持续时间中
                music_mode.time = 0;
                if (music_mode.cvol != music_mode.svol) {
                    // s_action.mvol = audio_music_get_volume();
                    if (music_mode.cvol < music_mode.svol) {
                        music_mode.cvol++;
                    } else if (music_mode.cvol > music_mode.svol) {
                        music_mode.cvol--;
                    }
                    app_music_play_vol(music_mode.cvol);  // 音乐音量设置
                }
            }
        }
    }
}
#endif

//==============================================================================================
// BLE电动床控制
static void bed_motor_control(uint8_t value)
{
    app_parse_data_t item;
    item.addr[0] = 0x57;
    item.addr[1] = 0x4C;
    item.value = (ARRAY_TYPE *)malloc(1 * sizeof(ARRAY_TYPE));
    ((ARRAY_TYPE *)item.value)[0] = value;
    motor_handler(item);
    free(item.value);
}
//==============================================================================================
// 向mesh设备发送数据并等待是否响应！
static bool mesh_handler_write_wait(const uint8_t addr[6], const char *data, uint8_t len, uint16_t wait_time)
{
    xEventGroupClearBits(xEvent, USER_MESH_EVENT);  // 记得先清标识
    mdf_err_t mesh_ret = app_mwifi_root_write(addr, MWIFI_TYPE_NODE, data, len); // 发给指定节点设备
    #ifdef APP_USER_DEBUG_ENABLE 
    ESP_LOGI(TAG, "mesh_ret = %d", mesh_ret);
    #endif
    if (mesh_ret == MDF_OK) {
        if (wait_time == 0 || (user_event_wait(USER_MESH_EVENT, wait_time) == true)) { // 等待MESH响应
            return true;  // 标记为MESH设备，只做数据转发，退出JSON解析循环！
        }
    }
    return false;  // error!!!
}

// 向mesh设备发现JSON格式数据并等待
// len = 0; 为字符串格式的数据！
static bool mesh_handler_json_wait(const uint8_t addr[6], const char *name, const void *value, uint8_t len)
{
    cJSON *jroot = cJSON_CreateObject(); 
    if (jroot == NULL) return false;

    if (len > 0 && len < 8) {
        int intArray[8];
        for (uint8_t i = 0; i < len; i++) {
            intArray[i] = ((uint8_t *)value)[i];  // 把uint8_t 转成 int 类型
        }
        cJSON_AddItemToObject(jroot, name, cJSON_CreateIntArray(intArray, len));
    } else {
        cJSON_AddItemToObject(jroot, name, cJSON_CreateString( (char *)value) );
    }
  
    char *jdata = cJSON_PrintUnformatted(jroot);
    #ifdef APP_USER_DEBUG_ENABLE 
    ESP_LOGI(TAG, "mesh_handler_write_wait = %s", jdata);
    #endif
    bool ret = mesh_handler_write_wait(addr, jdata, strlen(jdata), 400);  
   
    cJSON_free(jdata);
    cJSON_Delete(jroot);
    jroot = NULL;

    return ret;
}

//==============================================================================
// 窗帘电机控制
//==============================================================================
static const uint8_t curtain_mode_table[4] = {0xdd, 0xcc, 0xee, 0xcc};

static void curtain_motor_write(const char *name, uint8_t select, uint8_t data)
{
    uint8_t addr[6];
    bool ret = app_bind_find_addr_select("YiCurtain", select, addr);  // 根据名字找到设备addr
    if (ret == false) return;  // 没找到
    mesh_handler_json_wait(addr, name, &data, 1);
}

// 窗帘电机控制
static void curtain_motor_control(uint8_t select, uint8_t data)
{
    curtain_motor_write("ctrl", select, data);
}

// 窗帘电机行程控制
static void curtain_motor_travel(uint8_t select, uint8_t data)
{
    curtain_motor_write("travel", select, data);
}

// 舒曼波控制
static void sumanbo_control(uint8_t data)
{
    uint8_t addr[6];
    bool ret = app_bind_find_addr("YiSuman", addr);  // 根据名字找到设备addr
    if (ret == false) return;  // 没找到
    const char *name = NULL;
    char value[2] = { 0 };
    if (data <= 3) {  // 功率设置
        name = "level";
        value[0] = (data + 1) + '0';
    } else {
        name = "onoff";
        value[0] = (data - 4) + '0';
    } 
    mesh_handler_json_wait(addr, name, value, 0);  
}

static void ledc_control(const char *name, uint8_t *data, uint8_t len)
{
    uint8_t addr[6];
    bool ret = app_bind_find_addr("YiRgbcw", addr);  // 根据名字找到设备addr
    if (ret == false) return;  // 没找到
    mesh_handler_json_wait(addr, name, data, len);  
}

//==============================================================================================
//==============================================================================================
#define CASUAL_MODE     0X2E        // 休闲模式
#define READ_MODE       0X2F        // 阅读模式
#define SLEEP_MODE      0X31        // 深睡模式
#define ZERO_MODE       0X45        // 零压模式
#define TV_MODE         0X58        // TV模式
#define CUSTOM_MODE     0X59        // 记忆/自定义模式
#define STOP_MODE       0X00        // 暂停

#define APP_CONFIG_TEST         0
#if APP_CONFIG_TEST
bool motor_test = 0;
uint16_t motor_timecnt = 0;
uint16_t curtain_timecnt = 0;
static uint8_t motor_index = 0;
static uint8_t curtain_index = 0;
// APP演示程序
void app_test_task(void)
{
    static bool motor_test_last = false;
    if (motor_test_last != motor_test) {
        motor_test_last = motor_test;
        motor_timecnt = 5000;
        curtain_timecnt = 5000;
        if (motor_test == false) { // 暂停测试
            motor_index = 8;
            curtain_index = 1;
        }
        printf("motor_test = %d...\r\n", motor_test);
    }

    if (motor_test == false && motor_timecnt < 4000) {
        return;
    }

    if (++motor_timecnt >= 600)  {
        motor_timecnt = 0;
        // 能设置几分钟实现深睡  休闲 零压 TV 阅读 TV 零压 休闲 深睡 每隔几分钟（0-250）自动运行一次的指令
        const uint8_t motor_mode_table[] = {SLEEP_MODE, CASUAL_MODE, ZERO_MODE, TV_MODE, READ_MODE, TV_MODE, ZERO_MODE, CASUAL_MODE, 0x00};
        bed_motor_control(motor_mode_table[motor_index]);   // 电动床电机
        if (++motor_index >= 8)  motor_index = 0;
    }

    // return;

    // 窗帘电机指令
    if (++curtain_timecnt > 300) {
        curtain_timecnt = 0;
        curtain_motor_control(0, curtain_mode_table[curtain_index]);  // 窗帘电机
        if (++curtain_index >= 4)  curtain_index = 0;
    }
}
#endif

#if CONFIG_LVGL_ENABLE
// LVGL事件回调
static void lv_event_callback(lv_app_event_t event)
{
    ESP_LOGI(TAG, "lv_event_callback = { .windows = %d, .event = %d, .value = %d, .len = %d }", event.windows, event.event, event.value[0], event.len);
    switch (event.windows) {  
    case 0:  // WIN0界面
        switch (event.event) {
        case LV_EVENT_SHORT_CLICKED:
            #if APP_CONFIG_TEST
            motor_test = event.value[0];
            #endif
            break;
        case LV_EVENT_LONG_PRESSED: {
            #if 1
            app_parse_data_t item;
            clear_handler(item); // 清除网络
            sys_reset_enable = true;  // 使能软复位
            #endif
            break;
        }
        case LV_EVENT_LONG_PRESSED_REPEAT:
            #if 0
            nvs_bind_reset();         // 绑定数据全清
            esp_ota_reset_factory();  // 恢复到出厂固件
            sys_reset_enable = true;  // 使能软复位
            #endif
            break;
        default:
            break;
        }
        break;
    case 1:  // WIN1界面- MP3播放界面   

        break; 
    case 2:  // WIN2界面
        if (event.event == LV_EVENT_CLICKED) {  // 电动床控制
            if (event.value[0] < 6) {
                const uint8_t motor_mode_table[7] = {READ_MODE, TV_MODE, CUSTOM_MODE, CASUAL_MODE, SLEEP_MODE, ZERO_MODE, 0x00};
                bed_motor_control(motor_mode_table[event.value[0]]);   
            } else {  // 模式
                #if SCENE_LOCAL_ENABLE
                const char *name = NULL;
                if (event.value[0] == 6) {
                    name = SCENE_NAME_WAKEUP;
                } else if (event.value[0] == 7) {
                    name = SCENE_NAME_SLEEP;
                } else {
                    // name = "3";   // TV模式
                }
                if (name != NULL) {
                    scene_update_action(name, SCENE_ACTION_START);  // 执行情景模式
                }
                #endif
            }
        } else if (event.event == LV_EVENT_VALUE_CHANGED) {
            #if SCENE_LOCAL_ENABLE
            scene_update_action(s_action.name, SCENE_ACTION_STOP);  // 退出情景模式
            #endif
        }
        break;
    case 3: {  // WIN3界面
        if (event.event == LV_EVENT_CLICKED) {  // 窗帘电机控制
            curtain_motor_control(event.value[1], curtain_mode_table[event.value[0]]);  // 窗帘电机
        } else if (event.event == LV_EVENT_VALUE_CHANGED) {  // 行程控制
            curtain_motor_travel(event.value[1], event.value[0]);
        }
        break;
    }
    case 4:  // WIN4界面
        if (event.event == LV_EVENT_CLICKED) {  // 舒曼波控制
            sumanbo_control(event.value[0]);  // 舒曼波控制
        }
        break;
    case 5: {  // WIN5界面   HSVCW
        const char *name = NULL;
        if (event.event == 'O') {  // "Onoff"
            name = "onoff";
        } else if (event.event == 'H') {  // "Hue"
            name = "hsv";
        } else if (event.event == 'C') {  // "Cool"
            name = "cool";
        } else if (event.event == 'W') {  // "Warm"
            name = "warm";
        } else if (event.event == 'V') {  // "Vaule"
            name = "hsv";
        }
        ledc_control(name, event.value, event.len);
        #ifdef APP_USER_DEBUG_ENABLE  // debug
        ESP_LOGI(TAG, "Ledc: %s\n", name);
        #endif
        break;  
    }
    default:
        break;    
    }    
}
#endif

//=============================================================================================================== 
//=============================================================================================================== 
// 用户逻辑功能任务
static void app_user_task(void *arg)
{ 
    #if !CONFIG_LVGL_ENABLE
    hsv_t hsv_last = { 0 };
    hsv.saturation = 100;  
    #endif

    union8_t net_curr = { 0 };  // 当前网络状态
    union8_t net_last = { 0 };  // 上次网络状态
    #define NET_BLE_STATE       bits.bit0
    #define NET_WIFI_STATE      bits.bit1
    #define NET_SERVER_STATE    bits.bit2

    #define NET_BLE_ICON        bits.bit4
    #define NET_WIFI_ICON       bits.bit5
    #define NET_SERVER_ICON     bits.bit6
 
    if (nvs_wifi.status != WIFI_CONFIG_NULL) {  // 配过成功了！
        audio_voice = VOICE_COMM_WIFI_CONNECTING;  // 提示正在连接网络
    }
 
    while (true) {
        // START...
        //========================================================================================
 
        //=============================================================================
        /* 情景模式 */
        scene_handle_task();

        vTaskDelay(100);  /* 主任务执行的周期/MS */  

        #if 1
        static uint8_t freetime = 0;
        if (++freetime > 50) {
            freetime = 0;
            esp_free_heap_print();
        }
        #endif

        #if !CONFIG_LVGL_ENABLE
        //=============================================================================
        /* 长按5s清除配网信息 & 恢复出厂设置 */
        static uint8_t key_time = 0;
        static uint8_t key_last = 0;
        uint8_t key_value = key_scan_task();
        if (key_value != 0xFF) { // key
            if (++key_time > 30) {  // 5s
                #if 1
                if (key_value == KEY_RESET_FACTORY) {  // IO0
                    nvs_bind_reset();         // 绑定数据全清
                    esp_ota_reset_factory();  // 恢复到出厂固件
                } else if (key_value == KEY_CLEAR_NETWORK) {   // IO21
                    app_parse_data_t item;
                    clear_handler(item); // 清除网络
                }
                sys_reset_enable = true;  // 使能软复位
                #endif
            }
            key_last = key_value;
            #ifdef APP_USER_DEBUG_ENABLE  // debug
            // printf("key_value = %d\r\n", key_value);
            #endif
        } else {
            if (key_last && key_time >= 1 && key_time <= 9) {  // 单击
                motor_test = !motor_test;
            }
            key_last = 0;
            key_time = 0;
        }
        #endif

        //=============================================================================
        /* UDP定时广播 */
        #if !APP_CONFIG_MQTT_ENABLE
        upd_fixed_time_adv(10);  
        #endif
  
        //=============================================================================
        /* APP演示模式 */
        #if APP_CONFIG_TEST
        app_test_task();
        #endif
       
        //=============================================================================
        /* OTA升级状态 */
        static int ota_plan_last = 0;
        static uint8_t ota_timecnt = 100;
        int ota_plan = mwifi_mupgrade_status();
        if ((ota_plan != 0 && ++ota_timecnt >= 20) || ota_plan >= 100) {  // 正在OTA, 定时2S上报一次进度
            if (ota_plan_last != ota_plan) {  // 变化了再上报！
                ota_timecnt = 0;
                if (ota_plan_last == 0)  {  
                    audio_voice = VOICE_COMM_OTA;  // 提示OTA开始
                } else if (ota_plan >= 100) {
                    audio_voice = VOICE_COMM_OTA_OK;
                    sys_reset_enable = true;  // 使能软复位
                } 
                ota_plan_last = ota_plan;
                #ifdef APP_USER_DEBUG_ENABLE  // debug
                ESP_LOGI(TAG, "ota_plan: %d\n", ota_plan);
                #endif
                char ota_str[5];
                itoa(ota_plan, ota_str, 10);
                app_upper_cloud_format(app_get_ota_addr(), mid_ota_value, "ota", (char *)ota_str, 0);  // 发送OTA进度
            }
        }  
 
        //=============================================================================
        if (nvs_wifi.status != WIFI_CONFIG_NULL) {  // 配网成功了！
            //=============================================================================
            /* 配网成功了 */
            if (sys_config_network == true) {
                sys_config_network = false;
                ble_gatts_deinit_handle();   // 先注销GATTS
                app_ble_gattc_create();      // 再初始化GATTC
                net_curr.NET_BLE_STATE = true;
            }
            
            //=============================================================================
            /* WIFI连接状态 */
            net_curr.NET_WIFI_STATE = mwifi_connect_status(0);
            if (net_last.NET_WIFI_STATE != net_curr.NET_WIFI_STATE) {
                net_last.NET_WIFI_STATE = net_curr.NET_WIFI_STATE;
                if (net_curr.NET_WIFI_STATE == true) {  // WIFI刚连接上
                    audio_voice = VOICE_COMM_WIFI_CONNECT_OK;
                } else {  // WIFI刚断开连接
                    audio_voice = VOICE_COMM_WIFI_DISCONNECT;
                }
            }   
    
            //=============================================================================
            /* 服务器连接状态 */
            #if APP_CONFIG_MQTT_ENABLE
            net_curr.NET_SERVER_STATE = mqtt_connect_status(0);
            #else 
            net_curr.NET_SERVER_STATE = tcp_client_connect_status();
            #endif
            if (net_last.NET_SERVER_STATE != net_curr.NET_SERVER_STATE) {
                net_last.NET_SERVER_STATE = net_curr.NET_SERVER_STATE;
                if (net_curr.NET_SERVER_STATE == true) {   // MQTT连接成功 
                    audio_voice = VOICE_COMM_SERVER_CONNECT_OK;  // 每次连接上MQTT时，都上报一次版本号！
                    vTaskDelay(500);
                    app_upper_cloud_format(self.addr, "0", "ver", (char *)self.version,  0);
                } else { // MQTT刚断开连接
                    audio_voice = VOICE_COMM_SERVER_DISCONNECT;
                } 
            }  
            //=============================================================================
            #if !CONFIG_LVGL_ENABLE
            /* 指示灯 */
            if (wifi_status == true) {  // wifi连接上了
                if (server_status == true) {  // 服务器连接成功
                    if (ota_plan > 0) {  // 在OTA中
                        if (hsv.hue != 60) {
                            hsv.value = 10;
                            hsv.hue = 60;
                        } else {
                            hsv.value = 10;
                            hsv.hue = 300;
                        }
                    } else if (ble_gattc_all_conn_status()) {
                        hsv.hue = 240;
                        hsv.value = 10;
                    } else {
                        hsv.hue = 200;
                    }
                } else {
                    hsv.hue = 120;
                }
            } else {
                hsv.hue = 30;
            }
            #endif
        } else {  // 还没配网
            #if !CONFIG_LVGL_ENABLE
            hsv.hue = 0;
            #endif
            //=============================================================================
            /* GATTS 连接状态 */
            net_curr.NET_BLE_STATE = ble_connect_status();
            if (net_last.NET_BLE_STATE != net_curr.NET_BLE_STATE) {
                net_last.NET_BLE_STATE = net_curr.NET_BLE_STATE;
                if (net_curr.NET_BLE_STATE == true) {  // 开始配网
                    audio_voice = VOICE_COMM_WIFI_PAIR;
                } else {  // 配网失败
                    audio_voice = VOICE_COMM_WIFI_PAIR_FAIL; 
                }
            }
        }
        #if !CONFIG_LVGL_ENABLE
        static uint8_t led_cnt = 0;
        if (++led_cnt >= 8 && ota_plan == 0 && hsv.hue < 240) {  // 闪烁
            led_cnt = 0;
            if (hsv.value) {
                hsv.value = 0;
            } else {
                hsv.value = 20;
            }
        }
        #endif
 
        #if !CONFIG_LVGL_ENABLE
        //=============================================================================
        /* RGB灯 */
        if (sys_reset_enable == true) {  // 复位时，亮红灯
            hsv.value = 10;
            hsv.hue = 0;
        }
        if (hsv_last.hue != hsv.hue || hsv_last.value != hsv.value || hsv_last.saturation != hsv.saturation) {  // 有变化！
            hsv_last.hue = hsv.hue;
            hsv_last.value = hsv.value;
            hsv_last.saturation = hsv.saturation;
            app_rgb_set_hsv(hsv);
        }  
        #endif
        /** 图标显示 */
        #if CONFIG_LVGL_ENABLE
        static uint16_t icon_timecnt = 0;
        if (++icon_timecnt > 5) {   // 网络图标显示; 这个时间必须大于LVGL初始化时间
            icon_timecnt -= 5;
            // BLE Status...
            if (net_curr.NET_BLE_STATE == false) {
                net_curr.NET_BLE_ICON = !net_curr.NET_BLE_ICON;
            } else {
                net_curr.NET_BLE_ICON = true;
            }   
            if (net_last.NET_BLE_ICON != net_curr.NET_BLE_ICON) {
                net_last.NET_BLE_ICON = net_curr.NET_BLE_ICON;
                lv_ble_icon_switch(net_curr.NET_BLE_ICON);
            }
            // WIFI Status...
            if (net_curr.NET_WIFI_STATE == false) {
                net_curr.NET_WIFI_ICON = !net_curr.NET_WIFI_ICON;
            } else {
                net_curr.NET_WIFI_ICON = true;
            }   
            if (net_last.NET_WIFI_ICON != net_curr.NET_WIFI_ICON) {
                net_last.NET_WIFI_ICON = net_curr.NET_WIFI_ICON;
                lv_wifi_icon_switch(net_curr.NET_WIFI_ICON);
            }
            // Server Status...
            if (net_curr.NET_SERVER_STATE == false) {
                net_curr.NET_SERVER_ICON = !net_curr.NET_SERVER_ICON;
            } else {
                net_curr.NET_SERVER_ICON = true;
            }   
            if (net_last.NET_SERVER_ICON != net_curr.NET_SERVER_ICON) {
                net_last.NET_SERVER_ICON = net_curr.NET_SERVER_ICON;
                lv_server_icon_switch(net_curr.NET_SERVER_ICON);
            }
        }
        #endif
 
        //=============================================================================
        /* 软复位 */
        static uint8_t reset_time = 0;
        if (sys_reset_enable == true) {
            if (++reset_time == 3) { 
                app_ble_gap_close_all();       // 断开BLE连接
                bluedroid_stack_deinit();
                #if APP_CONFIG_MQTT_ENABLE
                app_mqtt_client_disconnect();  // 断开MQTT连接   
                wifi_clear_connect_status();   // 清除WIFI连接状态
                #else
                wifi_sock_close();             // 关闭sock连接
                #endif
                audio_voice = VOICE_COMM_POWER_DOWN;
            } else if (reset_time > 6) { 
                #ifdef APP_USER_DEBUG_ENABLE  // debug
                ESP_LOGI(TAG, "\n\nesp_restart...\n\n");
                #endif
                app_mwifi_deinit();         // 关闭wifi
                esp_restart();              // 系统软复位！
            }
        } 

        //=============================================================================
        //=============================================================================
        /* 语音播报！ */
        static uint8_t audio_voice_last = 0xFF;
        /* 跌倒报警 */
        if (tumble_status) {
            if (++tumble_time > 90) {
                tumble_time = 0;
                audio_voice = VOICE_COMM_TUBLE_WARING;
                audio_voice_last = 0;
            }
        }
        if (audio_voice_last != audio_voice) {
            audio_voice_last = audio_voice;
            #if !CONFIG_LVGL_ENABLE
            app_voice_speech(audio_voice); 
            #else 
            audio_music_play_audio(audio_voice);
            #endif
        }
 
        //========================================================================================
        // END...
    }
}

//=============================================================================================================== 
//=============================================================================================================== 

static void mesh_ver_handler(uint8_t addr[6], const char *name, void *value)
{
    // 接收到节点的版本{"ver":01.00.01}；就同步时间给他
    char sntp_time[24];
    uint8_t size = sntp_str_systime(sntp_time);  // 获取时间搓
    if (size > 0) {
        app_mwifi_root_write(addr, MWIFI_TYPE_NODE, sntp_time, size); // 发给指定节点设备
    }
} 

// 跌倒传感器的值
static void mesh_tumble_handler(uint8_t addr[6], const char *name, void *value)
{
    const ARRAY_TYPE *data = (ARRAY_TYPE *)value;
    uint16_t tcmd = strtol(name, NULL, 16);  // "0x0101" 十六进制字符串 to 数字
    switch (tcmd) {
    case 0x8301:  // 跌倒状态
        tumble_status = data[0];
        tumble_time = 100;
        break;
    case 0x8001:  // 有无人状态
        if (data[0] == 0) {  // 无人状态
            tumble_status = 0;
        }  
        break;
    default:
        break;
    }
} 

typedef struct {
    const char *name;
    void (*handler)(uint8_t addr[6], const char *name, void *value);
} mesh_handler_type_t;

static const mesh_handler_type_t mesh_handler_table[] = {
    { "ver",       mesh_ver_handler        },
    { "0x8",       mesh_tumble_handler     }
}; 

#define MESH_TABLE_SIZE    (sizeof(mesh_handler_table) / sizeof(mesh_handler_type_t)) 
//===================================================================================================================
//===================================================================================================================
// WIFI Mesh 接收任务 （其他Mesh节点发给ROOT节点的数据）
static void mwifi_read_callback(mwifi_read_t info)
{
    #ifdef APP_USER_DEBUG_ENABLE  // debug
    ESP_LOGI(TAG, "mwifi_read_callback[Root] addr: " MACSTR ", type: 0x%02x, data: %s | %d", MAC2STR(info.addr), info.data_type.custom, info.data, info.size);
    #endif
#if !CONFIG_LVGL_ENABLE 
    if (hsv.value < 20) {
        hsv.value = 30;  
    } else {
        hsv.value = 10;
    }
#endif

    switch (info.data_type.custom) {
    case MWIFI_TYPE_ROOT:  // 其它节点发给ROOT的数据
        // app_mwifi_root_write(info.addr, MWIFI_TYPE_NODE, info.data, info.size); 
        break;
    case MWIFI_TYPE_APP: { // 其它节点发给APP的数据
        /*********************************** 解析数据 **********************************/
        cJSON *jroot = cJSON_Parse((char *)info.data);  
        if (jroot == NULL)  break;
        /*=======================================================================================*/
        /** 网关把节点的数据都解析一遍，看看有没有用的! */
        for (uint8_t size = 0; size < cJSON_GetArraySize(jroot); size++) {  // 先看JSON大小
            cJSON *item = cJSON_GetArrayItem(jroot, size);  // 取值
            if (cJSON_IsNull(item))  break; // 为空
            for (uint8_t i = 0; i < MESH_TABLE_SIZE; i++) {  // 遍历有没有网关想要的数据
                if (strncmp(item->string, mesh_handler_table[i].name, strlen(mesh_handler_table[i].name)) == 0) {
                    void *value = NULL;
                    if (item->type == cJSON_String) {
                        value = item->valuestring;
                    } else if (item->type == cJSON_Array) {
                        uint8_t size = cJSON_GetArraySize(item);
                        value = (ARRAY_TYPE *)malloc(size * sizeof(ARRAY_TYPE));
                        for (uint8_t i = 0; i < size; i++) {
                            cJSON *aitem = cJSON_GetArrayItem(item, i);  // 取值
                            ((ARRAY_TYPE *)value)[i] = aitem->valueint;
                        }
                    }
                    mesh_handler_table[i].handler(info.addr, item->string, value);
                    if (item->type == cJSON_Array) {  // 数据类型记得释放内存
                        free(value);
                    }
                    break;
                }
            }
        } /*=======================================================================================*/
        /*=======================================================================================*/
   
        char mac_addr[13];
        mlink_mac_hex2str(info.addr, mac_addr);
        cJSON_AddItemToObject(jroot, "addr", cJSON_CreateString(mac_addr) );   // 添加"addr"

        #if APP_CONFIG_MQTT_ENABLE
        cJSON_AddItemToObject(jroot, "mid", cJSON_CreateString(mid_value) );
        #endif

        char *cjson_data = cJSON_PrintUnformatted(jroot);
        if (cjson_data == NULL) break; 
        uint16_t data_len = strlen(cjson_data);

        #ifdef APP_USER_DEBUG_ENABLE     
        // ESP_LOGI(TAG, "mesh->app_upper_write = %s | %d", cjson_data, data_len);
        #endif 
 
        app_upper_cloud_write(cjson_data, data_len);

        cJSON_free(cjson_data);
        cJSON_Delete(jroot); 

        xEventGroupSetBits(xEvent, USER_MESH_EVENT);  // Mesh响应成功！ 
        #if SCENE_LOCAL_ENABLE
        /* 情景之设备状态触发 */
        for (uint8_t i = 0; i < trigger.total; i++) {
            #if 0 // #ifdef TAG
            ESP_LOGI(TAG, "trigger.event[%d].name = %s", i, trigger.event[i].name);
            ESP_LOGI(TAG, "trigger.event[%d].addr = "MACSTR"", i, MAC2STR(trigger.event[i].addr));
            ESP_LOGI(TAG, "trigger.event[%d].instruct = %s", i, trigger.event[i].instruct);
            #endif
            if (memcmp(trigger.event[i].addr, info.addr, 6) == 0) {  // 先对比地址
                if (strcmp(trigger.event[i].instruct, (char *)info.data) == 0) { // 再对比指令字段
                    const char *action = NULL;
                    if (trigger.event[i].dtime == 0) {  // 没有持续时间，则为起始动作 
                        action = SCENE_ACTION_START;
                    } else {
                        action = SCENE_ACTION_STOP;
                    }
                    #ifdef APP_USER_DEBUG_ENABLE
                    ESP_LOGI(TAG, "[ scene ] trigger.event[%d].instruct = %s\n\n\n", i, trigger.event[i].instruct);
                    #endif
                    scene_update_action(trigger.event[i].name, action);
                    break;
                }
            }
        }
        #endif

        break;
    }
    case MWIFI_TYPE_TOPO: // 其它节点发的TOPO心跳包！
        // app_mqtt_publish_topo((const char *)info.data, info.size); 
        break;    
    default:
        break;
    }  
}

//=====================================================================================
//=====================================================================================
// BLE 配网
static const char *separator = "|";   // 分隔符

// {"wifi":"<SSID>|<PASSWORD>"} // {"wifi":"yiree-zhulao|yiree2021|YIREF6E5D4C3B2A1"}
// {"wifi":"ok/fail"}    
static char *wifi_handler(char *data)
{
    char *temp = strtok(data, separator);
    for (uint8_t index = 0; temp != NULL; index++) {
        uint8_t len = strlen(temp);
        #ifdef APP_USER_DEBUG_ENABLE     
        ESP_LOGI(TAG, "wifi_temp[%d] = %s | %d", index, temp, len);
        #endif
        switch (index) {
        case 0:
            memcpy(nvs_wifi.ssid, temp, len); 
            nvs_wifi.ssid[len] = '\0';  
            break;
        case 1:
            memcpy(nvs_wifi.password, temp, len);  
            nvs_wifi.password[len] = '\0'; 
            break;   
        default:
            break;
        }
        temp = strtok(NULL, separator);
    }  

    if (app_mwifi_reconnect(nvs_wifi.ssid, nvs_wifi.password) == true) {  // 连接成功了！
        nvs_wifi.status = WIFI_CONFIG_WIFI;         // WIFI配网成功 
        #if !APP_CONFIG_MQTT_ENABLE
        nvs_wifi_handle(&nvs_wifi, NVS_READWRITE);  // 存储NVS
        audio_voice = VOICE_COMM_WIFI_PAIR_OK;  // 配网成功
        sys_config_network = true;  // 配网成功
        #endif
        return "ok";
    } else {
        audio_voice = VOICE_COMM_WIFI_PAIR_FAIL;
        return "fail";
    }
}

// {"mqtt":"<HOST>|<PORT>|<USERNAME>|<USERPASSWORD>|<QOS>"} 
// 比如：{"mqtt":"iot.yiree.com.cn|1883|YiRoot|b0f7eb1accf6|0"}
// {"mqtt":"ok/fail"}
#if APP_CONFIG_MQTT_ENABLE 
static char *mqtt_handler(char *data)
{
    char *temp = strtok(data, separator);
    for (uint8_t index = 0; temp != NULL; index++) {
        uint8_t len = strlen(temp); 
        #ifdef APP_USER_DEBUG_ENABLE     
        ESP_LOGI(TAG, "mqtt_temp[%d] = %s | %d", index, temp, len);
        #endif
        switch (index) {
        case 0:  // <HOST>
            memcpy(nvs_mqtt.host, temp, len); 
            nvs_mqtt.host[len] = '\0';  
            break;
        case 1: // <PORT>
            nvs_mqtt.port = atoi(temp); // 字符串 转 数字
            break;
        case 2: // <USERNAME>   
            memcpy(nvs_mqtt.username, temp, len);  
            nvs_mqtt.username[len] = '\0';  
            break;
        case 3: // <USERPASSWORD>
            memcpy(nvs_mqtt.userword, temp, len);  
            nvs_mqtt.userword[len] = '\0';  
            break;
        case 4:  // <QOS> = atoi(temp); 
            nvs_mqtt.qos = temp[0] - '0';
            break;
        default:
            break;
        }
        temp = strtok(NULL, separator);
    }  
 
    if (app_mqtt_client_reconnect(nvs_mqtt) == true) { // 连接MQTT成功
        nvs_mqtt.status = MQTT_CONFIG_OK;           // MQTT配网成功 
        nvs_mqtt_handle(&nvs_mqtt, NVS_READWRITE);  // 存储NVS
        nvs_wifi_handle(&nvs_wifi, NVS_READWRITE);  // 存储NVS
        #if APP_CONFIG_MQTT_ENABLE
        audio_voice = VOICE_COMM_WIFI_PAIR_OK;  // 配网成功
        sys_config_network = true;  // 配网成功
        #endif
        return "ok";
    } else {  // fail...
        audio_voice = VOICE_COMM_WIFI_PAIR_FAIL;
        return "fail";
    }
}
#endif   
typedef struct {
    const char *name;
    char *(*handler)(char *);
} handler_type_t; 
static const handler_type_t ble_handler_table[] = {
    { "wifi",       wifi_handler        },
#if APP_CONFIG_MQTT_ENABLE
    { "mqtt",       mqtt_handler        }
#endif    
}; 

#define BLE_TABLE_SIZE    (sizeof(ble_handler_table) / sizeof(handler_type_t))

// BLE_GATTS接收回调函数
void gatts_task_callback(uint8_t idx, uint8_t *data, uint8_t len)
{
    #ifdef APP_USER_DEBUG_ENABLE     
    ESP_LOGI(TAG, "ble_callback = %s | %d", data, len);
    // esp_log_buffer_hex("ble_rx", data, len);
    #endif

    cJSON *jroot = cJSON_Parse((char *)data);    
    if (jroot == NULL) {  // ERROR!!!
        #ifdef APP_USER_DEBUG_ENABLE  // debug
        ESP_LOGE(TAG, "BLE cJSON_Parse error");
        #endif
        return;
    }

    for (uint8_t i = 0; i < BLE_TABLE_SIZE; i++) {
        cJSON *item = cJSON_GetObjectItem(jroot, ble_handler_table[i].name);
        if (item != NULL) { 
            const char *result = ble_handler_table[i].handler(item->valuestring);
            if (result != NULL) {  // 有数据就响应！
                cJSON_ReplaceItemInObject(jroot, item->string, cJSON_CreateString(result));
                char *cjson_data = cJSON_PrintUnformatted(jroot);
                #ifdef APP_USER_DEBUG_ENABLE     
                ESP_LOGI(TAG, "gatts_result = %s", cjson_data);
                #endif
                ble_indicate_sendto_app((uint8_t *)cjson_data, strlen(cjson_data));  // 发给BLE
                cJSON_free(cjson_data);
            }
            break; 
        }
    }
 
    /* Remember to free memory */
    cJSON_Delete(jroot);  
    jroot = NULL;
}

//===================================================================================
//===================================================================================
//===================================================================================
static char *ver_handler(app_parse_data_t item)
{
    // app_upper_cloud_format(self.addr, "0", "ver", (char *)self.version,  0);
    return self.version;
}

static char *clear_handler(app_parse_data_t item)
{
    if (nvs_wifi.status != WIFI_CONFIG_NULL) {
        nvs_wifi.status = WIFI_CONFIG_NULL;
        audio_voice = VOICE_COMM_SERVER_DISCONNECT;       
    }
    nvs_wifi_reset();          // 清除配网信息！
#if APP_CONFIG_MQTT_ENABLE   
    nvs_mqtt_reset();           // 清除配网信息！
#endif    
    sys_reset_enable = true;   // 使能软复位
    return "ok";
}

static char *ota_handler(app_parse_data_t item)
{
    strcpy(mid_ota_value, mid_value);  // 复制OTA的消息ID
    app_mupgrade_start(item.addr, (char *)item.value);  // 启动OTA
    return NULL;
}
 
static char *sntp_handler(app_parse_data_t item)
{
    // {"addr":"000000000000","sntp":"1680161030"}
    char *value = (char *)item.value;
    value[11] = '\0';
    sntp_sync_time_custom( atoi(value) + 1 ); // 校准时间（+1s网关延时）
    app_mwifi_update_time(); // 更新同步时间  
    return NULL;
}

// {"bind":"<MAC>|<NAME>"} 
// {"bind":"ok/fail"}    
static char *bind_handler(app_parse_data_t item)
{
    char *value = (char *)item.value;
 
    // 判断分割线 || 简单判断设备名是否正确
    if (value[12] != separator[0] || (value[13] != 'C' && value[13] != 'Y')) {
        #ifdef APP_USER_DEBUG_ENABLE     
        ESP_LOGW(TAG, "bind_handler ERR = %s", value);
        #endif
        return "fail"; 
    } 

    value[12] = '\0';  // '|' ===>>> '\0'
    uint8_t addr[6];
    mlink_mac_str2hex(value, addr); // 设备地址
 
    bool ret = app_bind_add(addr, value + 13);  // 添加绑定并更新
    if (ret == true) {
        return "ok"; 
    } else {
        return "fail"; 
    }
}

static char *mesh_handler(app_parse_data_t item);

static char *unbind_handler(app_parse_data_t item)
{
    char *value = (char *)item.value;

    if (strcmp(value, "all") == 0) {  // 全部解绑
        app_bind_remove_all();  // 全部解绑
        return "ok"; 
    }
 
    value[12] = '\0';  // '|' ===>>> '\0'
    uint8_t addr[6];
    mlink_mac_str2hex(value, addr); // 设备地址
    const char *name = app_bind_find_name(addr);
    bool ret = app_bind_remove(addr);  // 删除绑定设备  
    if (ret == true) {
        if (name != NULL && (name[0] == 'Y' && name[1] == 'i')) {
            mesh_handler(item);  // 解绑mesh设备
        }
        return "ok"; 
    } else {
        return "fail"; 
    } 
}


static char *motor_handler(app_parse_data_t item)
{
    ARRAY_TYPE *value = (ARRAY_TYPE *)item.value;  // 数组格式
    uint8_t motor_buff[5]; // = { 0x6E, 0x01, 0x00, 0x24, 0x00 };
    motor_buff[0] = 0X6E;
    motor_buff[1] = 0x01;
    motor_buff[2] = 0x00;
    motor_buff[3] = value[0];
    motor_buff[4] = 0x6F + motor_buff[3];  // 计算校验和
    xEventGroupClearBits(xEvent, USER_MESH_EVENT);  // 记得先清标识
    int ble_ret = app_ble_gattc_write_char(item.addr, motor_buff, 5);   // 通过BLE发送数据到BLE设备
    #ifdef APP_USER_DEBUG_ENABLE 
    ESP_LOGI(TAG, "motor_handler["MACSTR"] = 0x%x", MAC2STR(item.addr), value[0]);
    ESP_LOGI(TAG, "ble_ret = %d", ble_ret);
    #endif
    if (item.is_scene == true) return NULL;   // 情景模式不等待响应！ 
    if (ble_ret == pdTRUE) {  // write ok!
        if ((user_event_wait(USER_BLE_EVENT, 500) == true)) {  // 等待BLE设备响应！（所有MQTT消息，都需要一条一条等待响应之后才能才能执行一条！）
            return NULL;  // OK!!!
        }
    }
    return "fail"; // error!!!
}



// WIFI-MESH处理
static char *mesh_handler(app_parse_data_t item)
{
    // mesh数据不做任何处理，直接转发！！！
    char *data = cJSON_PrintUnformatted(item.json);
    if (data == NULL) return 0; 
    uint8_t len = strlen(data);
    #ifdef APP_USER_DEBUG_ENABLE     
    ESP_LOGI(TAG, "mesh_handler["MACSTR"] = %s | %d", MAC2STR(item.addr), data, len);
    #endif  
    uint16_t wait_time;
    if (item.is_scene == true) { // 情景模式不等待响应！ 
        wait_time = 0;
    } else {
        wait_time = 2000;
    }
    bool ret = mesh_handler_write_wait(item.addr, data, len, wait_time); // 等待MESH响应
    cJSON_free(data);  
    if (ret == true) {
        return "mesh";  // 标记为MESH设备，只做数据转发，退出JSON解析循环！
    }
    return "fail";  // error!!!
}

#if CONFIG_LVGL_ENABLE 
#include "bsp_sdspi.h"

// 上报MP3列表
bool app_upper_cloud_mlist(const uint8_t addr[6], const char *mid, const char *name)
{
    cJSON *jroot = cJSON_CreateObject(); 
    if (jroot == NULL) return 0;
 
    char mac_addr[13];
    mlink_mac_hex2str(addr, mac_addr);
    cJSON_AddItemToObject(jroot, "addr", cJSON_CreateString(mac_addr) );
    #if APP_CONFIG_MQTT_ENABLE
    cJSON_AddItemToObject(jroot, "mid", cJSON_CreateString(mid) );
    #endif
 
    DIR *dir = NULL;  
    struct dirent *entry;    
    if ( (dir = opendir(MOUNT_POINT"/MP3")) == NULL) {  
        #ifdef TAG
        ESP_LOGI(TAG, "MP3 opendir failed!");  
        #endif
        return 0;  
    }

    uint8_t count = 0;
    const char *mlist[32] = { NULL };
    while ( (entry = readdir(dir)) != NULL) { 
        mlist[count++] = entry->d_name;
    }  
    closedir(dir);   

    cJSON_AddItemToObject(jroot, name, cJSON_CreateStringArray(mlist, count));
  
    char *cjson_data = cJSON_PrintUnformatted(jroot);
    if (cjson_data == NULL) return 0; 
    uint16_t data_len = strlen(cjson_data);

    #ifdef APP_USER_DEBUG_ENABLE     
    ESP_LOGI(TAG, "app_wifi_sock_write[mp3] = %s | %d", cjson_data, data_len);
    #endif   
 
    app_upper_cloud_write(cjson_data, data_len);

    cJSON_free(cjson_data);
    cJSON_Delete(jroot);
    jroot = NULL;

    return 1;
}

// 获取音量列表
static char *mlist_handler(app_parse_data_t item)
{
    app_upper_cloud_mlist(item.addr, mid_value, item.name);
    return NULL;
}

// 音乐控制
static char *mctrl_handler(app_parse_data_t item)
{
    char *value = (char *)item.value;
 
    audio_ctrl_t audio_ctrl = AUDIO_CTRL_NONE;
    bool play_status = true;

    if (value[0] >= '0' && value[0] <= '9') {
        audio_music_play_mp3( atoi(value) ); 
    } else {  // 其它指令
        const char *ctrl_table[4] = {"stop", "run", "last", "next"};
        for (uint8_t i = 0; i < 4; i++) {
            if (strcmp(value, ctrl_table[i]) == 0) {
                switch (i) {
                case 0:
                    audio_ctrl = AUDIO_CTRL_PAUSED;
                    play_status = false;
                    music_mode.dtime = 0;  // 暂停播放；持续时间清零
                    break;
                case 1:
                    audio_ctrl = AUDIO_CTRL_RUNNING; 
                    break;
                case 2:
                    audio_ctrl = AUDIO_CTRL_LAST;
                    break;  
                case 3:
                    audio_ctrl = AUDIO_CTRL_NEXT;
                    break;       
                default:
                    break;
                } 
            }
        }
    }
             
    app_music_play_ctrl(play_status, audio_ctrl);
 
    return value;
}

// 设置音乐音量大小
static char *mvol_handler(app_parse_data_t item)
{
    char *value = (char *)item.value;
    uint8_t mvol = atoi(value);
#if SCENE_LOCAL_ENABLE
    if (item.is_scene == true) {  // 情景解析
        if (strcmp(s_action.name, SCENE_NAME_SLEEP) == 0) {  // 睡眠模式
            s_action.mvol = mvol;
            s_action.set_mvol = 0;
        } else if (strcmp(s_action.name, SCENE_NAME_WAKEUP) == 0) {  // 唤醒模式
            s_action.mvol = 2;
            s_action.set_mvol = mvol;
        } 
    }  
#endif
    app_music_play_vol( mvol );
   
    return value;
}
#endif

#if SCENE_LOCAL_ENABLE
// 添加情景模式
static char *sadd_handler(app_parse_data_t item)
{
    char fname[5];  // 文件名
    // const char *fname = NULL;  // 这里不能不用指针，因为有删除
    cJSON *name_item = cJSON_GetObjectItem(item.json, "name");
    if (name_item == NULL)  return "fail";
    strcpy(fname, name_item->valuestring);
    cJSON_DeleteItemFromObject(item.json, "name"); // 删除"name"节点
    char *data = cJSON_PrintUnformatted(item.json);
    scene_write(fname, data);  // 写情景数据
    cJSON_free(data);
    return "ok";
}
// 删除情景
static char *sdel_handler(app_parse_data_t item)
{
    char *value = (char *)item.value;
    int ret = scene_delete(value);
    if (ret != 0) {
        return "fail";
    }
    return value;
}

// 情景触发/运行
static char *srun_handler(app_parse_data_t item)
{
    char *value = (char *)item.value;
 
    const char *name   = strtok(value, separator);
    const char *action = strtok(NULL,  separator);
    // 这里有bug, 如果此时有情景正在执行，会被覆盖！
    bool ret = scene_update_action(name, action);  
    if (ret == false) return "fail";

    return value;
}
#else // 在线情景
uint8_t app_parse_handler(app_parse_data_t parse, cJSON *item);
// 在线情景模式运行
static void scene_run_handler(cJSON *jroot)
{
    cJSON *dtime_item = cJSON_GetObjectItem(jroot, "dtime");
    if (dtime_item != NULL) {
        #ifdef TAG  // debug
        ESP_LOGI(TAG, "dtime = %s", dtime_item->valuestring);
        #endif
    }

    cJSON *action_item = cJSON_GetObjectItem(jroot, "action");
    if (action_item == NULL) {
        #ifdef TAG  // debug
        ESP_LOGE(TAG, "action_item error");
        #endif
        return;
    }
    if (action_item->type != cJSON_Array) return;  // 不是数组类型

    uint8_t jSize = cJSON_GetArraySize(action_item);
    for (uint8_t size = 0; size < jSize; size++) {
        cJSON *arr_item = cJSON_GetArrayItem(action_item, size);  // 遍历
        if (cJSON_IsNull(arr_item))  break; // 为空
        if (arr_item->type != cJSON_Object) continue;
        #ifdef TAG 
        char *data = cJSON_PrintUnformatted(arr_item);
        ESP_LOGI(TAG, "action[%d] = %s", size, data);
        cJSON_free(data);
        #endif
        //===========================================================================
        app_parse_data_t parse = { 0 };
        cJSON *addr_item = cJSON_GetObjectItem(arr_item, "addr");
        if (addr_item != NULL) {
            mlink_mac_str2hex(addr_item->valuestring, parse.addr); // 设备地址
            cJSON_DeleteItemFromObject(arr_item, "addr"); // 删除"addr"节点
            parse.json = arr_item;  // 记住当前的JSON原始数据
        }  
        /* ***************************** 功能指令集解析 ************************* */
        for (uint8_t index = 0; (addr_item != NULL && index < cJSON_GetArraySize(parse.json)); index++) {
            cJSON *item = cJSON_GetArrayItem(parse.json, index);  // 遍历
            if (cJSON_IsNull(item))  break; // 为空
            if (xSemaphoreTake(xSemap, portMAX_DELAY) == pdTRUE) {  // 等待获取互斥信号量
                parse.is_scene = true;  // 是情景模式，不用等待响应！
                int parse_ret = app_parse_handler(parse, item); // 解析设备指令集
                xSemaphoreGive(xSemap);  // 释放互斥信号量
                vTaskDelay(200);  // 稍微延时一下
                if (parse_ret == APP_PARSE_EXIT || parse_ret == APP_PARSE_NULL)  break;   
            }
        }
    }
}

// 音乐模式
static char *mmode_handler(app_parse_data_t item)
{
    uint8_t *value = (uint8_t *)item.value;

    music_mode.play = value[0];    // 播放第几首音乐
    music_mode.cvol = value[1];    // 当前音量大小
    music_mode.svol = value[2];    // 设置的音量大小
    music_mode.dtime= (value[3] >> 8) | value[4];    // 持续时间/s
    music_mode.interval = music_mode.dtime / abs(music_mode.svol - music_mode.cvol);// 时间间隔/S，interval = dtime / abs(set_mvol - mvol)
    music_mode.time = 0;    // 时间计数/s

    app_music_play_vol( music_mode.cvol );
    audio_music_play_mp3( music_mode.play ); 
    return NULL;
}
#endif

 
typedef struct {
    const char *name;
    char *(*handler)(app_parse_data_t);
} json_handler_t;

EXT_RAM_ATTR static const json_handler_t sys_handler_table[] = {
    /*********** 系 统 指 令 ***********/
    { "ver",        ver_handler         },
    { "sntp",       sntp_handler        },
    { "ota",        ota_handler         },
    { "bind",       bind_handler        },
    { "unbind",     unbind_handler      },
#if CONFIG_LVGL_ENABLE 
    /* music 音乐 */
    { "mlist",      mlist_handler       },
    { "mctrl",      mctrl_handler       },
    { "mvol",       mvol_handler        },
    { "mmode",      mmode_handler       },
#endif
#if SCENE_LOCAL_ENABLE
    /* scene 情景 */
    { "srun",       srun_handler        },
    { "sadd",       sadd_handler        },
    { "sdel",       sdel_handler        },
#endif
    { "clear",      clear_handler       }
}; 

#define SYS_TABLE_SIZE    (sizeof(sys_handler_table) / sizeof(json_handler_t))
//========================================================================================
EXT_RAM_ATTR static const json_handler_t app_handler_table[] = {
    /*********** 设 备 指 令 ***********/
    { "CERM",      motor_handler       },
    { "Yi",        mesh_handler        },
    { "ota",       ota_handler         }
}; 

#define APP_TABLE_SIZE    (sizeof(app_handler_table) / sizeof(json_handler_t))

//========================================================================================
//========================================================================================
//========================================================================================


// 解析设备指令集
uint8_t app_parse_handler(app_parse_data_t parse, cJSON *item)
{
    // 系统指令集（注意：mesh设备也支持OTA!）
    if (memcmp(parse.addr, self.addr, 6) == 0 || (parse.addr[0] | parse.addr[1]) == 0) {
        for (uint8_t i = 0; i < SYS_TABLE_SIZE; i++) {  // 遍历键名
            if (strcmp(item->string, sys_handler_table[i].name) == 0) {
                switch (item->type) {   
                case cJSON_String:
                    parse.size  = 0;  // 字符串类似长度为0
                    parse.value = item->valuestring;
                    parse.name  = item->string;
                    #ifdef APP_USER_DEBUG_ENABLE  
                    ESP_LOGI(TAG, "%s_handler: %s", sys_handler_table[i].name, (char *)parse.value);
                    #endif
                    break;
                case cJSON_Array:   
                    parse.size  = cJSON_GetArraySize(item);
                    parse.value = (ARRAY_TYPE *)malloc(parse.size * sizeof(ARRAY_TYPE));
                    for (uint8_t i = 0; i < parse.size; i++) {
                        cJSON *aitem = cJSON_GetArrayItem(item, i);  // 取值
                        ((ARRAY_TYPE *)parse.value)[i] = aitem->valueint;
                    }
                    ESP_LOGI(TAG, "%s_handler: %d", sys_handler_table[i].name, ((ARRAY_TYPE *)parse.value)[0]);
                    break;
                case cJSON_Object:  
                    parse.json = item;   // 记录下当前的cJSON_Object
                    break;
                default:  // error type
                    break;
                }    
 
                const char *result = sys_handler_table[i].handler(parse);
                if (result != NULL) {  // 有数据就响应！
                    #ifdef APP_USER_DEBUG_ENABLE     
                    ESP_LOGI(TAG, "result = %s", result);
                    #endif
                    app_upper_cloud_format(parse.addr, mid_value, item->string, (char *)result, 0);
                }
                if (item->type == cJSON_Array) {  /** 是数组类型记得释放内存!!! */
                    free(parse.value);  // 记得释放内存
                }
                break;
            }
        }
        return APP_PARSE_ROOT;
    }

    if (parse.name == NULL) {  // 还没有找到设备名
        parse.name = app_bind_find_name(parse.addr);  // 根据addr找到设备名
        if (parse.name != NULL) {
            #ifdef APP_USER_DEBUG_ENABLE 
            ESP_LOGI(TAG, "find dev_name = %s", parse.name);
            #endif
        } else {  
            #ifdef APP_USER_DEBUG_ENABLE  
            esp_log_buffer_hex("Fail addr", parse.addr, 6);
            #endif
            return APP_PARSE_NULL;
        }
    }
 
    // 根据BLE设备名来单独处理特定设备
    for (uint8_t i = 0; i < APP_TABLE_SIZE; i++) {  // 遍历键名
        if (strncmp(parse.name, app_handler_table[i].name, strlen(app_handler_table[i].name)) == 0) {  
            switch (item->type) {   
            case cJSON_String:
                parse.size  = 0;  // 字符串类型大小特定为0
                parse.value = item->valuestring;
                ESP_LOGI(TAG, "%s_handler: %s", app_handler_table[i].name, (char *)parse.value);
                break;  
            case cJSON_Array: { 
                // #define ARRAY_TYPE  int    
                parse.size  = cJSON_GetArraySize(item);
                parse.value = (ARRAY_TYPE *)malloc(parse.size * sizeof(ARRAY_TYPE));
                for (uint8_t i = 0; i < parse.size; i++) {
                    cJSON *aitem = cJSON_GetArrayItem(item, i);  // 取值
                    ((ARRAY_TYPE *)parse.value)[i] = aitem->valueint;
                }
                ESP_LOGI(TAG, "%s_handler: %d", app_handler_table[i].name, ((ARRAY_TYPE *)parse.value)[0]);
                break;
            }                            
            default:  
                return APP_PARSE_NULL;
            } 

            if (parse.value != NULL) {
                const char *result = app_handler_table[i].handler(parse); /** 根据name 执行任务 */
                if (item->type == cJSON_Array) {  /** 是数组类型记得释放内存!!! */
                    free(parse.value);  // 记得释放内存
                }
                if (result != NULL && parse.is_scene == false) {
                    ESP_LOGI(TAG, "app_handler_table->result: %s", result); 
                    if (strcmp(result, "fail") == 0) { // 错误
                        app_upper_cloud_format(parse.addr, mid_value, item->string, (char *)result, 0);
                    } 
                    return APP_PARSE_EXIT;
                }  
                return APP_PARSE_NODE;
            }
            break;
        }
    }
    return APP_PARSE_NULL;
}

// 解析云端数据任务
static void app_parse_cloud_task(char *data)
{
    app_parse_data_t parse = { 0 };
    
    cJSON *jroot = cJSON_Parse(data);    
    if (jroot == NULL) {  // ERROR!!!
        #ifdef APP_USER_DEBUG_ENABLE  // debug
        ESP_LOGE(TAG, "Sock cJSON_Parse error");
        #endif
        return;
    }

    //  在线情景执行
#if !SCENE_LOCAL_ENABLE
    cJSON *scene_item = cJSON_GetObjectItem(jroot, "srun");
    if (scene_item != NULL) {
        scene_run_handler(scene_item);  // 执行情景模式
        cJSON_Delete(jroot);  
        jroot = NULL;
        return;
    }
#endif

    cJSON *addr_item = cJSON_GetObjectItem(jroot, "addr");
    if (addr_item != NULL) {
        mlink_mac_str2hex(addr_item->valuestring, parse.addr); // 设备地址
        cJSON_DeleteItemFromObject(jroot, "addr"); // 删除"addr"节点
        parse.json = jroot;  // 记住当前的JSON原始数据
    }  

    #if APP_CONFIG_MQTT_ENABLE
    cJSON *mid_item = cJSON_GetObjectItem(jroot, "mid");
    if (mid_item != NULL) {  
        strcpy(mid_value, mid_item->valuestring);  // 得到消息ID
        cJSON_DeleteItemFromObject(jroot, "mid");   // 删除"mid"节点
    } else {
        mid_value[0] = '0';
    }
    #endif
 
    /* ***************************** 功能指令集解析 ************************* */
    for (uint8_t index = 0; (parse.json != NULL && index < cJSON_GetArraySize(parse.json)); index++) {
        cJSON *item = cJSON_GetArrayItem(parse.json, index);
        if (cJSON_IsNull(item))  break; // 为空
        /********************************************************************/
        if (xSemaphoreTake(xSemap, portMAX_DELAY) == pdTRUE) {  // 等待获取互斥信号量
            int parse_ret = app_parse_handler(parse, item); // 解析设备指令集
            xSemaphoreGive(xSemap);  // 释放互斥信号量
            if (parse_ret == APP_PARSE_EXIT || parse_ret == APP_PARSE_NULL)  break;   
        }
    }

    /* Remember to free memory */
    cJSON_Delete(jroot);  
    jroot = NULL;
}
 
#if APP_CONFIG_MQTT_ENABLE
// MQTT 接收任务 {O}
static void mqtt_subscribe_callback(mqtt_data_t info)
{ 
    #ifdef APP_USER_DEBUG_ENABLE  // debug
    // ESP_LOGI(TAG, "Free heap, current: %d, minimum: %d", esp_get_free_heap_size(), esp_get_minimum_free_heap_size());  // 打印内存
    ESP_LOGI(TAG, "mqtt_subscribe_callback->data: %s | %d", info.data, info.len);
    #endif
    app_parse_cloud_task((char *)info.data);  
}
#else  
static void wifi_sock_recv_callback(sock_data_t info)
{
 
    #ifdef APP_USER_DEBUG_ENABLE     
    ESP_LOGI(TAG, "Socket Received[%d] : %s | %d", info.sock, info.data, info.len); 
    #endif

    app_parse_cloud_task((char *)info.data);  
}
#endif

//===================================================================================================================
//===================================================================================================================
// 接收BLE设备消息任务
static void gattc_read_callback(const uint8_t addr[6], uint8_t *data, uint16_t len)
{
#ifdef APP_USER_DEBUG_ENABLE  // debug   
    // esp_log_buffer_hex("gattc_read_callback->ble_addr", addr, 6);
    // esp_log_buffer_hex("gattc_read_callback->ble_data", data, len);
    // ESP_LOGI(TAG, "gattc_read_callback->ble_data: %s | %d", data, len);
#endif
    const char *name = NULL;
    if (addr[0] == 0x57 && addr[1] == 0x4C) {  // 是电机
        name = "motor";
        data[0] = data[3];  // 取控制字
        len = 1;
    } 
    if (name != NULL) {
        app_upper_cloud_format(addr, mid_value, name, (uint8_t *)data, len);
        xEventGroupSetBits(xEvent, USER_BLE_EVENT);  // BLE响应成功！
    }
}

//=====================================================================================
//=====================================================================================
bool get_sys_config_network(void)
{
    return nvs_wifi.status == WIFI_CONFIG_NULL ? false : true;  // 是否配网成功了
}

//===================================================================================================================
//===================================================================================================================
void app_user_init(void) 
{
#ifdef APP_USER_DEBUG_ENABLE  // debug    
    ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
    char chip_name[16] = { 0 };
    app_read_chip_name(chip_name);
    ESP_LOGI(TAG, "[APP] Chip name: %s\n\n", chip_name);
#endif
    mid_value[0] = '0';
    xSemap = xSemaphoreCreateMutex();      // 创建互斥量
    xEvent = xEventGroupCreate();          // 设备消息事件组

    app_get_self_info(&self);  // 读设备自己的信息 
    nvs_wifi_handle(&nvs_wifi, NVS_READONLY);   // 先读WIFI信息

#if CONFIG_LVGL_ENABLE 
    lv_event_register_callback(lv_event_callback);  // 记得先注册回调函数
    audio_adf_init();  
    app_lvgl_init();  //return;
    // audio_adf_init();  
#else     
    app_gpio_init();    // GPIO输入输出初始化！
    app_voice_init();  
    app_rgb_init();
    app_scene_init(); 
#endif
    xTaskCreatePinnedToCore(app_user_task, "app_user", 4 * 1024, NULL, 5, NULL, APP_CPU_NUM);
    vTaskDelay(200);  // 等待硬件外设初始化完成！ 释放一些内存再初始化BLE&WIFI
    // return;
#if 1
    /***************************** Bluedroid LE ****************************** */
    ESP_ERROR_CHECK( bluedroid_stack_init() );    // 初始化"BLE协议栈"
    gattc_register_callback(gattc_read_callback); // 注册GATTC接收回调函数. 
    /* BLE 要在WIFI之前先初始化，不然会报错： coexist: [2202407] Error! Should enable WiFi modem sleep when both WiFi and Bluetooth a */
    if (nvs_wifi.ssid[0] == '\0' || nvs_wifi.password[0] == '\0') {  // 说明没有配网
        app_ble_gatts_init();   // GATTS配网
        ble_gatts_register_callback(gatts_task_callback);
        sys_config_network = false;
    } else {
        sys_config_network = true;  // 配网成功，初始化GATTC
    } 
#endif  
    /***************************** Wi-Fi ****************************** */
    app_mwifi_init(nvs_wifi);  
    mwifi_register_callback(mwifi_read_callback);

#if APP_CONFIG_MQTT_ENABLE
    nvs_mqtt_handle(&nvs_mqtt, NVS_READONLY);   
    app_mqtt_init(&nvs_mqtt);  
    mqtt_subscribe_register_callback(mqtt_subscribe_callback);
#else // TCP/UDP
    wifi_sock_init();
    wifi_sock_register_callback(wifi_sock_recv_callback);  
#endif  
    /* END...................................................................................*/
}
/* END...................................................................................*/
// 查看错误码信息： https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/error-codes.html
// http://wiki.tjc1688.com/doku.php 

#if 0
 LVGL 内存池使用PSRAM,并使能TE同步，可以节约内部内存！
 ../components/lvgl/src/misc/lv_mem.c:81
 CONFIG_ENABLE_LCD_TE
#endif
